﻿using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Common.Internal.Logging;
using DotNetty.Handlers.Logging;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NetC100K.Handler;
using NetC100K.Setting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;

namespace NetC100K
{
    public class ServerPool : IDisposable
    {
        private ILogger _logger;
        private ServerSetting _serverSetting;
        private ConcurrentDictionary<string, IChannel> _channels;
        public ConcurrentDictionary<string, IChannel> Channels { get { return _channels; } }

        /// <summary>
        /// IP过滤数组，该数组内IP不算连接
        /// </summary>
        private string[] _filters;

        /// <summary>
        /// 监听Channel
        /// </summary>
        private IChannel _boundChannel;

        /// <summary>
        /// 主线程组
        /// </summary>
        private IEventLoopGroup _bossGroup;

        /// <summary>
        /// 工作线程组
        /// </summary>
        private IEventLoopGroup _workerGroup;

        public ServerPool(ILoggerFactory factory)
        {
            try
            {
                _logger = factory.CreateLogger<ServerPool>();
                _channels = new ConcurrentDictionary<string, IChannel>();

                //InternalLoggerFactory.DefaultFactory = factory;
                Environment.SetEnvironmentVariable("io.netty.allocator.numHeapArenas", "0");
                Environment.SetEnvironmentVariable("io.netty.allocator.numDirectArenas", "0");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "ServerPool初始化时发生错误");
            }
        }

        /// <summary>
        /// 启动服务
        /// 所有Channel 对应一个 channelHandler,此时必须要保证该channelHandler是线程安全的
        /// </summary>
        /// <param name="port"></param>
        /// <param name="servername"></param>
        /// <param name="handler"></param>
        /// <returns></returns>
        public async Task<bool> Start(ServerSetting serverSetting, string[] filters)
        {
            _filters = filters;
            _serverSetting = serverSetting;

            if (_serverSetting.IP != null) { }
            if (_serverSetting.Port <= IPEndPoint.MinPort || _serverSetting.Port >= IPEndPoint.MaxPort)
            {
                throw new ArgumentOutOfRangeException($"端口不符合取值范围，取值范围：大于{IPEndPoint.MinPort}，小于{IPEndPoint.MaxPort}");
            }

            _bossGroup = new MultithreadEventLoopGroup(1);
            _workerGroup = new MultithreadEventLoopGroup();

            try
            {
                var bootstrap = new ServerBootstrap();
                bootstrap.Group(_bossGroup, _workerGroup)
                    .Channel<TcpServerSocketChannel>()
                #region Option说明
                    /*
                     * ChannelOption.SO_BACKLOG
                     * 对应的是tcp/ip协议listen函数中的backlog参数，函数listen(int socketfd,int backlog)用来初始化服务端可连接队列，服务端处理客户端连接请求是顺序处理的，所以同一时间只能处理一个客户端连接，多个客户端来的时候，服务端将不能处理的客户端连接请求放在队列中等待处理，backlog参数指定了队列的大小
                     * 
                     * ChanneOption.SO_REUSEADDR
                     * 对应于套接字选项中的SO_REUSEADDR，这个参数表示允许重复使用本地地址和端口，比如，某个服务器进程占用了TCP的80端口进行监听，此时再次监听该端口就会返回错误，使用该参数就可以解决问题，该参数允许共用该端口，这个在服务器程序中比较常使用，比如某个进程非正常退出，该程序占用的端口可能要被占用一段时间才能允许其他进程使用，而且程序死掉以后，内核一需要一定的时间才能够释放此端口，不设置SO_REUSEADDR就无法正常使用该端口。
                     * 
                     * Channeloption.SO_KEEPALIVE
                     * 参数对应于套接字选项中的SO_KEEPALIVE，该参数用于设置TCP连接，当设置该选项以后，连接会测试链接的状态，这个选项用于可能长时间没有数据交流的连接。当设置该选项以后，如果在两小时内没有数据的通信时，TCP会自动发送一个活动探测数据报文。
                     * 
                     * ChannelOption.SO_SNDBUF
                     * 参数对应于套接字选项中的SO_SNDBUF，ChannelOption.SO_RCVBUF参数对应于套接字选项中的SO_RCVBUF这两个参数用于操作接收缓冲区和发送缓冲区的大小，接收缓冲区用于保存网络协议站内收到的数据，直到应用程序读取成功，发送缓冲区用于保存发送数据，直到发送成功。
                     * 
                     * ChannelOption.SO_LINGER
                     * 参数对应于套接字选项中的SO_LINGER,Linux内核默认的处理方式是当用户调用close（）方法的时候，函数返回，在可能的情况下，尽量发送数据，不一定保证会发生剩余的数据，造成了数据的不确定性，使用SO_LINGER可以阻塞close()的调用时间，直到数据完全发送
                     * 
                     * ChannelOption.TCP_NODELAY
                     * 参数对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关,Nagle算法是将小的数据包组装为更大的帧然后进行发送，而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了，组装成大的数据包进行发送，虽然该方式有效提高网络的有效负载，但是却造成了延时，而该参数的作用就是禁止使用Nagle算法，使用于小数据即时传输，于TCP_NODELAY相对应的是TCP_CORK，该选项是需要等到发送的数据量最大的时候，一次性发送数据，适用于文件传输。
                     * 
                     * IP
                     * 设置IP头部的Type-of-Service字段，用于描述IP包的优先级和QoS选项。
                     * 
                     * Netty参数，
                     * 一个连接的远端关闭时本地端是否关闭，默认值为False。值为False时，连接自动关闭；为True时，触发ChannelInboundHandler的userEventTriggered()方法，事件为ChannelInputShutdownEvent。
                     * 
                     * 服务器端TCP内核模块维护有2个队列，我们称之为A，B吧，客户端向服务端connect的时候，发送带有SYN标志的包（第一次握手），服务端收到客户端发来的SYN时，向客户端发送SYN ACK 确认(第二次握手)，此时TCP内核模块把客户端连接加入到A队列中，然后服务器收到客户端发来的ACK时（第三次握手）TCP把客户端连接从A队列移到B队列，连接完成，应用程序的accept会返回，也就是说accept从B队列中取出完成三次握手的连接，A队列和B队列的长度之和是backlog,当A，B队列的长之和大于backlog时，新连接将会被TCP内核拒绝，所以，如果backlog过小，可能会出现accept速度跟不上，A.B 队列满了，导致新客户端无法连接，要注意的是，backlog对程序支持的连接数并无影响，backlog影响的只是还没有被accept 取出的连接
                     */
                #endregion
                    .Option(ChannelOption.SoBacklog, 50000)
                    .Handler(new LoggingHandler())
                    .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
                    {
                        IChannelPipeline pipeline = channel.Pipeline;

                        pipeline.AddFirst(new IPFilterHandler(_filters));

                        var bytes = System.Text.Encoding.UTF8.GetBytes("*");
                        var buf = Unpooled.CopiedBuffer(bytes);
                        pipeline.AddLast(new DelimiterBasedFrameDecoder(1024 * 1024, buf));

                        pipeline.AddLast(new SessionHandler(_channels, _logger));
                    }));
                _boundChannel = await bootstrap.BindAsync(_serverSetting.Port);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"ServerPool启动时发生错误");
                return false;
            }

            return true;
        }

        public void ConcurrentSend(Func<IChannel, string> action)
        {
            var channels = _channels.Values;
            foreach (var channel in channels)
            {
                try
                {
                    if (channel != null)
                    {
                        string message = action(channel);
                        byte[] bytes = System.Text.Encoding.UTF8.GetBytes($"{ message}*");
                        IByteBuffer initialMessage = Unpooled.CopiedBuffer(bytes);
                        channel.WriteAndFlushAsync(initialMessage);
                    }
                    else
                    {
                        _logger.LogWarning("发现未知连接，已经删除");
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"循环发送时发生错误");
                }
            }
        }

        /// <summary>
        /// 服务关闭
        /// </summary>
        public async Task Close()
        {
            if (_boundChannel != null) await _boundChannel.CloseAsync();
            if (_bossGroup != null) await _bossGroup.ShutdownGracefullyAsync();
            if (_workerGroup != null) await _workerGroup.ShutdownGracefullyAsync();
        }

        public async void Dispose()
        {
            await Close();
        }
    }
}
